11.View面试题

View 相关问题

measure

自定义 view 时,super.onMeasure 一定需要调用吗?为什么?

不一定

最顶层的 View,谁 measure 了?顶层的 MeasureSpec 怎么来的?

ViewRootImple 的 performMeasure();顶层的 MeasureSpec 通过 ViewRootImpl#getRootMeasureSpec 生成的

draw

同步刷新

丢帧 (掉帧),是说这一帧延迟显示还是丢弃不再显示?

延迟显示,因为缓存交换的时机只能等下一个 VSync 了。

布局层级较多/主线程耗时是如何造成丢帧的呢?

布局层级较多/主线程耗时 会影响 CPU/GPU 的执行时间,大于 16.6ms 时只能等下一个 VSync 了

避免丢帧的方法之一是保证每次绘制界面的操作要在 16.6ms 内完成,如果某次用户点击屏幕导致的界面刷新操作是在某一个 16.6ms 帧快结束的时候,那么即使这次绘制操作小于 16.6 ms,按道理不也会造成丢帧么?

代码里调用了某个 View 发起的刷新请求 invalidate,这个重绘工作并不会马上就开始,而是需要等到下一个 VSync 来的时候 CPU/GPU 才开始计算数据存到 Buffer,下下一帧数据屏幕才从 Buffer 拿到数据展示

也就是说一个绘制操作后,至少需要等 2 个 vsync,在第 3 个 vsync 信号到来才会真正展示

Android 每隔 16.6 ms 刷新一次屏幕到底指的是什么意思?是指每隔 16.6ms 调用 onDraw() 绘制一次么?

Android 每隔 16.6 ms 刷新一次屏幕其实是指底层会以这个固定频率来切换每一帧的画面,而这个每一帧的画面数据就是我们 App 在接收到屏幕刷新信号之后去执行遍历绘制 View 树工作所计算出来的屏幕数据。而 app 并不是每隔 16.6ms 的屏幕刷新信号都可以接收到,只有当 app 向底层注册监听下一个屏幕刷新信号之后,才能接收到下一个屏幕刷新信号到来的通知。而只有当某个 View 发起了刷新请求时,App 才会去向底层注册监听下一个屏幕刷新信号。

如果界面一直保持没变的话,那么还会每隔 16.6ms 刷新一次屏幕么?

只有当界面有刷新的需要时,我们 app 才会在下一个屏幕刷新信号来时,遍历绘制 View 树来重新计算屏幕数据。如果界面没有刷新的需要,一直保持不变时,我们 app 就不会去接收每隔 16.6ms 的屏幕刷新信号事件了,但底层仍然会以这个固定频率来切换每一帧的画面,只是后面这些帧的画面都是相同的而已。

measure/layout/draw 走完,界面就立刻刷新了吗?

不是。measure/layout/draw 走完后,只是 CPU 计算数据完成,会在下一个 VSync 到来时进行缓存交换,屏幕才能显示出来。

VSYNC 这个具体指啥?在屏幕刷新中如何工作的?

屏幕刷新使用 双缓存、三缓存,这又是啥意思呢?

双缓存是 Back buffer、Frame buffer,用于解决 screen tearing 画面撕裂。三缓存增加一个 Back buffer,用于减少 Jank。

有了同步屏障消息的控制就能保证每次一接收到屏幕刷新信号就第一时间处理遍历绘制 View 树的工作么?

只能说,同步屏障是尽可能去做到,但并不能保证一定可以第一时间处理。因为,同步屏障是在 scheduleTraversals() 被调用时才发送到消息队列里的,也就是说,只有当某个 View 发起了刷新请求时,在这个时刻后面的同步消息才会被拦截掉。如果在 scheduleTraversals() 之前就发送到消息队列里的工作仍然会按顺序依次被取出来执行。

View 绘制基础

View 的 measure、layout 和 draw

Activity 中的 view 是如何被添加上的?

wysbo

measure 测量

measure 基本流程

measure 流程

MeasureSpec?

MeasureSpec 表示的是⼀个 32 位的整形值,它的⾼ 2 位表示测量模式 SpecMode,低 30 位表示某种测量模式下的规格大小 SpecSize。MeasureSpec 是 View 类的⼀个静态内部类,⽤来说明应该如何测量这个 View,它有三种测量模式:

对于 DecorView 而言,它的 MeasureSpec 由窗口尺寸和其⾃身的 LayoutParams 共同决定;对于普通的 View,它的 MeasureSpec 由父视图的 MeasureSpec 和其⾃身的 LayoutParams 共同决定。
普通 View 的 MeasureSpec 的创建规则:当 parentSpecMode 为 EXACTLY 且 childLayoutParams 取具体值或 match_parent 时,childSpecMode 取 EXACTLY,当 parentSpecMode 为 AT_MOST 且仅 childLayoutParams 取具体值时,childSpecMode 才取 EXACTLY,剩下的三种情况均取 AT_MOST

ViewGroup 的如何计算子 View 的 MeasureSpce,getChildMeasureSpec

对于应用层 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定
ubngs

常用布局的 onMeasure

  1. **FrameLayout **
  1. LinearLayout
  1. RelativeLayout

layout 布局

根据 measure 子 View 所得到布局大小和布局参数,将子 View 放在合适的位置上

layout 流程

draw 绘制

draw 绘制流程

硬件加速和软件绘制绘制

硬件加速和软件绘制基础

jccri

与直接执行绘制命令相比,先将绘制命令记录在 DisplayList 中然后再执行有两个好处。
第一个好处是在绘制窗口的下一帧时,若某一个视图的 UI 没有发生变化,那么就不必执行与它相关的 Canvas API,即不用执行它的成员函数 onDraw,而是直接复用上次构建的 Display List 即可。
第二个好处是在绘制窗口的下一帧时,若某一个视图的 UI 发生了变化,但是只是一些简单属性发生了变化,例如位置和透明度等简单属性,那么也不必重建它的 Display List,而是直接修改上次构建的 Display List 的相关属性即可,这样也可以省去执行它的成员函数 onDraw。

硬件加速的优点是啥?

  1. 硬件加速可以减轻 CPU 的负担和主线程的负担,因为硬件加速分 CPU 构建和 GPU 绘制两个步骤,CPU 只需要执行构建部分即可,另外 GPU 绘制是在渲染线程进行,该线程是子线程,解放了主线程的压力。
  2. 在对 view 属性更改时,硬件加速下只需要更改对应 View 的 RenderNode 属性值,CPU 构建的工作几乎可以忽略,特别是在执行 alpha、旋转等动画时,性能极大提高,而纯软件绘制每一帧都需要重绘脏区交叉的所有 View

非硬件加速下 view 的绘制都是依赖于 bitmap 绘制,所以不管是属性变更还是全量更新都需要重新绘制 bitmap,系统唯一能优化的就是画布的大小,尽可能减少绘制区域来提升性能。但不管怎么优化,当 view 处于交叉重叠时,仍无法避免重复绘制和过度绘制的事实

硬件绘制和软件绘制区别对比
823zp

View 绘制面试题

measure 相关

FrameLayout 什么情况下子 view 会 measure 两次?

FrameLayout 的宽或高为 wrap_content(不为 Exactly)时,且子 view 大于 1 个的宽或高为 match_parent,测量 2 次

ViewRootImpl.scheduleTraversals 时系统准备注册下一次屏幕刷新信号之前,往主线程的消息队列中发送了一个同步屏障消息,为什么?

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 往主线程的消息队列中发送一个同步屏障
        // mTraversalBarrier是ViewRootImpl中的成员变量,用于移除同步屏障时使用
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 确保mTraversalRunnable第一时间得到执行。这里的token为null,后面回调会用到
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

什么是同步屏障?
同步屏障的作用就是为了确保:同步屏障之后的所有同步消息都将被暂停,得不到执行,直到调用了 removeSyncBarrier(token) 释放掉同步屏障,所有的同步消息将继续执行。也就是说,同步屏障之后的异步消息将会优先得到执行

添加同步屏障目的?

而 Choreographer 的 postCallback 会通过 Handler,发送一个定时的,异步消息,等待下一次 vsync 信号到来,会回调提交的 Runnable,这个 Runnable 就是 mTraversalRunnable。

在前面通过 ViewRootImpl 类中的 scheduleTraversals() 方法,发送的同步屏障消息,是为了确保 mTraversalRunnable 能够第一时间得到执行。其中,mTraversalRunnable 为 ViewRootImpl 中成员变量,具体实现为 TraversalRunnable。TraversalRunnable 则为 ViewRootImpl 中成员内部类

如何在 Activity 中获取某个 View 的宽⾼?

由于 View 的 measure 过程和 Activity 的生命周期方法不是同步执行的,如果 View 还没有测量完毕,那么获得的宽/高就是 0。所以在 onCreate、onStart、onResume 中均无法正确得到某个 View 的宽高信息。

  1. Activity/View#onWindowFocusChanged
  2. View.post(Runnable) 原因见:View.post为什么可以获取到View的宽高?
  3. ViewTreeObserver.addOnGlobalLayoutListener
  4. 手动 view.measure()

View.post 为什么可以获取到 View 的宽高?

  1. View 已经 attach 到 Window:post 的消息放在消息队列尾部,等执行的时候,view 已经 measure 好了,保证了能正确的获取宽高
  2. View 没有 attach 到 Window:会暂存到 HandlerActionQueue,等 View attach 后,在 dispatchAttachedToWindow 方法中通过 Handler 分发暂存的任务

invalidate、requestLayout

invalidate、postInvalidate 区别

invaliddate 和 postInvalidate 只会调用 View 的 onDraw 方法,onLayout 和 onMeasure 不会调用
invalidate() 与 postInvalidate() 都⽤于刷新 View,主要区别是 invalidate() 在主线程中调用,若在子线程中使⽤需要配合 handler;而 postInvalidate() 可在子线程中直接调用。

invalidate 会不会导致 onMeasure 和 onLayout 被调用呢?

invalidate 中,在 performTraversals 方法中,mLayoutRequested 为 false,所有 onMeasure 和 onLayout 都不会被调用,只会调用 onDraw

requestLayout 与 invalidate 的区别?

  1. requestLayout 只会触发 measure 和 layout;invalidate 只会触发 draw

requestLayout 最终调用到 ViewRootImpl.requestLayout,最后执行 performMeasure→performLayout→performDraw;如果没有改变 l,t,r,b,那就不会触发 onDraw

  1. requestLayout 最终调用到 ViewRootImpl.requestLayout 会调用 checkThread 方法,而 invalidate 不会